package cn.zzq.aix.nativeinterface.jsplay;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Map;
import java.util.MissingResourceException;

import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeFunction;
import org.mozilla.javascript.NativeJavaObject;
import org.mozilla.javascript.NativeJavaPackage;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;

import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.annotations.UsesLibraries;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.ComponentContainer;
import com.google.appinventor.components.runtime.EventDispatcher;
import com.google.appinventor.components.runtime.util.RetValManager;
import com.google.appinventor.components.runtime.util.YailList;
import com.google.appinventor.components.runtime.util.YailProcedure;
import com.google.appinventor.components.runtime.util.YailProcedure.Executable;

import gnu.math.DFloNum;

@DesignerComponent(nonVisible = true, //
		category = ComponentCategory.EXTENSION, //
		iconName = "images/extension.png", //
		description = "一个js脚本引擎，可用于执行js代码（不包含DOM操作）以及与本地java对象的直接交互", //
		version = 1)
@UsesLibraries(libraries = "rhino-1.7.7.1.jar")
@SimpleObject(external = true)
public class JsPlay extends AndroidNonvisibleComponent {
	private boolean outputinfo = false;
	private boolean propertyListener=false;
	public org.mozilla.javascript.Context rhino;
	public ScriptableObject scope;

	public JsPlay(ComponentContainer container) {
		super(container.$form());
		Enter();
	}
	
	@SimpleFunction(description="导入类，className为类名,as填写导入类的名称，"
			+ "若为空，则以简化类名作为访问时的类名。特别注意java.lang.*"
			+ "中的类Boolean,Date,Math,Number,String与js内建对象重名，请不要导入这些类")
	public void ImportClass(String className,String as) {
		if (as.isEmpty()) {
			Class<?> class1;
			try {
				class1 = Class.forName(className);
				ImportClass(className, class1.getSimpleName());
			} catch (ClassNotFoundException e) {
				throw new RuntimeException("找不到类："+className);
			}
		}else {
			EvaluteJsWithoutReturn("导入类", "var "+as+"="+className);
		}
	}
	

	
	@SimpleFunction(description="删除全局属性")
	public void DeleteGlobalProperty(String name) {
		scope.delete(name);
	}
	@SimpleEvent(description="变量监测事件，当变量值发生改变时被调用")
	public void PropertyChanged(String name,Object oldValue,Object newValue,Object source) {
		EventDispatcher.dispatchEvent(this, "PropertyChanged", name,oldValue,newValue,source);
	}
	
	@SimpleFunction(description = "启动js引擎")
	public void Enter() {
		rhino = Context.enter();
		rhino.setOptimizationLevel(-1); // 设置运行级别为解释运行（android不支持编译运行）
		scope = rhino.initStandardObjects();//初始化一个标准js脚本对象
		
		PutGlobalProperty("ImportClass", ToJsFunction(new YailProcedure(new Executable() {
			
			@Override
			public int numArgs() {
				return 2;
			}
			
			@Override
			public Object execute(Object... args) {
				ImportClass((String)args[0], (String)args[1]);
				return null;
			}
		})));
	}

	@SimpleFunction(description = "关闭js引擎,移除上下文和当前的线程并做垃圾回收")
	public void Exit() {
		Context.exit();
	}

	@SimpleFunction(description = "运行js代码不返回值")
	public void EvaluteJsWithoutReturn(String codeName, String jsCode) {
		EvaluteJs(codeName, jsCode);
	}

	@SimpleFunction(description = "运行js代码,codeName为代码标识，不影响代码运行，仅用于当代码发生错误时方便定位到错误代码")
	public Object EvaluteJs(String codeName, String jsCode) {
		Object object;
		/*
		 * 参数解释: Scope : 运行的作用域,运行js时会在作用域 scope 中寻找所需要的变量. Source: 待运行的JS脚本字符串.
		 * SourceName: 定义源码的名称,出错的时候方便定位,可以为null. Lineno: 发生错误时显示的行号. securityDomain:
		 * 一个指定安全性的任意对象,如果不关心线程安全是,可以为null值.
		 */
		try {
			object = rhino.evaluateString(scope, jsCode, codeName, 0, null);
			if (outputinfo) {
				Log(codeName, object.toString());
			}
			return object;
		} catch (RhinoException e) {
			throw new RuntimeException("js代码有误，错误代码所在代码段名：" + e.sourceName() + "\n第" + e.lineNumber() + "行,第"
					+ e.columnNumber() + "个字符处\n" + "错误代码:" + e.lineSource() + "\n错误信息：" + e.getMessage());
		}catch (MissingResourceException e) {
			String key=e.getKey();
			String className=e.getClassName();
			String message=e.getMessage();
			throw new RuntimeException("Key:"+key+"\nClassName:"+className+"\nMessage:"+message);
		}
	}

	@SimpleFunction(description = "将一个对象转换为js对象")
	public Object ToJsObject(Object object) {
		return Context.javaToJS(object, scope);
	}

	@SimpleFunction(description = "从js引擎里取出一个对象")
	public Object GetGlobalProperty(String name) {
		Object object = ScriptableObject.getProperty(scope, name);
		if (object == Scriptable.NOT_FOUND) {
			throw new RuntimeException(name + "在JsPlay中未定义");
		} else {
			return object;
		}
	}

	@SimpleFunction(description = "获取js引擎的所有全局变量名")
	public YailList GetGlobalPropertyNames() {
		Map<?, ?> map = (Map<?, ?>) EvaluteJs("GetGlobalPropertyNames", "this");
		return YailList.makeList(map.keySet());
	}

	@SimpleFunction(description = "将一个对象传入js引擎中")
	public void PutGlobalProperty(String name, Object object) {
		ScriptableObject.putProperty(scope, name, object);
	}

	@SimpleFunction(description = "将js对象转换为ai的文本")
	public static String ToString(Object jsObject) {
		return Context.toString(jsObject);
	}

	@SimpleFunction(description = "将js数字转换为ai的数字")
	public static double ToNumber(Object jsObject) {
		return Context.toNumber(jsObject);
	}

	@SimpleFunction(description = "将js布尔值转换为ai的逻辑值")
	public static boolean ToBoolean(Object jsObject) {
		return Context.toBoolean(jsObject);
	}

	@SimpleFunction(description = "调用一个js函数")
	public Object CallJsFunction(Object jsFunction, YailList arguments) {
		if (jsFunction instanceof Function) {
			return ((Function) jsFunction).call(rhino, scope, scope, arguments.toArray());
		} else {
			throw new RuntimeException(jsFunction + "不是一个js函数");
		}
	}

	@SimpleFunction(description = "调用一个全局js函数")
	public Object CallGlobalFunction(String name, YailList arguments) {
		Object jsfunction = GetGlobalProperty(name);
		return CallJsFunction(jsfunction, arguments);
	}

	// YailProcedure -> NativeFunction
	@SimpleFunction(description = "ai过程转js函数")
	public Object ToJsFunction(final YailProcedure procedure) {
		return new NativeFunction() {

			private static final long serialVersionUID = 1989419157126259989L;

			@Override
			protected String getParamOrVarName(int index) {
				return "args" + index;
			}

			@Override
			protected int getParamCount() {
				return procedure.numArgs();
			}

			@Override
			protected int getParamAndVarCount() {
				return procedure.numArgs();
			}

			@Override
			protected int getLanguageVersion() {
				return getLanguageVersion();
			}

			@Override
			public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
				for (int i = 0; i < args.length; i++) {
					if (args[i] instanceof NativeFunction) {
						args[i] = ToProcedure(args[i]);
					}
				}
				Object object = procedure.call(args);
				if (object instanceof YailProcedure) {
					object = ToJsFunction((YailProcedure) object);
				} else if (object instanceof Number) {
					object = new DFloNum(((Number) object).doubleValue());
				}
				return object;
			}
		};
	}

	@SimpleFunction(description = "js函数转ai的过程")
	public YailProcedure ToProcedure(Object jsFunction) {
		final NativeFunction function = (NativeFunction) jsFunction;
		return new YailProcedure(new Executable() {
			@Override
			public int numArgs() {
				return function.getLength();
			}

			@Override
			public Object execute(Object... args) {
				for (int i = 0; i < args.length; i++) {
					if (args[i] instanceof YailProcedure) {
						args[i] = ToJsFunction((YailProcedure) args[i]);
					}
				}
				Object object = function.call(rhino, scope, scope, args);
				if (object instanceof NativeFunction) {
					object = ToProcedure(object);
				}
				return object;
			}
		});
	}

	@SimpleFunction(description = "拆包")
	public static Object Unwrap(Object wrapedObject) {
		if (wrapedObject instanceof NativeObject) {
			// 若为js数组，拆包方式:
			return JsArrayToList(wrapedObject);
		} else if (wrapedObject instanceof NativeObject) {
			// 若为js对象，拆包方式:
			return JsObjectToList(wrapedObject);
		} else if (wrapedObject instanceof NativeJavaObject) {
			// 若为java数组，java类，java对象，拆包方式：
			// NativeJavaArray,NativeJavaClass均继承自NativeJavaObject
			return ((NativeJavaObject) wrapedObject).unwrap();
		} else if (wrapedObject instanceof NativeJavaPackage) {
			return ((NativeJavaPackage) wrapedObject).getClassName();
		} else {
			return wrapedObject;
		}
	}

	@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN, defaultValue = "False")
	@SimpleProperty(description = "启用调试功能，当每段js代码被执行时，结果返回至调试输出面板")
	public void EnableOutputInfo(boolean enable) {
		this.outputinfo = enable;
	}

	@SimpleProperty
	public boolean EnableOutputInfo() {
		return outputinfo;
	}

	@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN, defaultValue = "False")
	@SimpleProperty(description = "启用变量监测器")
	public void EnablePropertyListener(boolean enable) {
		this.propertyListener=enable;
		if (enable) {
			rhino.addPropertyChangeListener(new PropertyChangeListener() {
				
				@Override
				public void propertyChange(PropertyChangeEvent e) {
					PropertyChanged(e.getPropertyName(), e.getOldValue(), e.getNewValue(), e.getSource());
				}
			});
		}
	}

	@SimpleProperty
	public boolean EnablePropertyListener() {
		return this.propertyListener;
	}
	
	@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_INTEGER, defaultValue = "0")
	@SimpleProperty(description = "设置JavaScript版本，不同版本语法特性可能不同，"
			+ "详情见：https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript"
			+ "格式为版本号×100，如JavaScript1.8为180。" + "注意ES6标准为200,JsPlay默认版本为0,未知则为-1")
	public void LanguageVersion(int version) {
		rhino.setLanguageVersion(version);
	}

	@SimpleProperty(description = "")
	public int LanguageVersion() {
		return rhino.getLanguageVersion();
	}

	@SimpleFunction(description = "递归地将js数组转换为列表")
	public static Object JsArrayToList(Object jsArray) {
		if (jsArray instanceof NativeArray) {
			NativeArray nativeArray = (NativeArray) jsArray;
			ArrayList<Object> result = new ArrayList<Object>();
			for (Object object : nativeArray) {
				if (object instanceof NativeArray) {
					object = JsArrayToList(object);
				} else if (object instanceof NativeObject) {
					object = JsObjectToList(object);
				}
				result.add(object);
			}
			return YailList.makeList(result);
		}
		return jsArray;
	}

	@SimpleFunction(description = "将JS对象递归地转换为ai列表键值对")
	public static Object JsObjectToList(Object jsObject) {
		if (jsObject instanceof NativeObject) {
			NativeObject objectMap = (NativeObject) jsObject;
			ArrayList<Object> result = new ArrayList<Object>();
			for (Object key : objectMap.keySet()) {
				Object value = objectMap.get(key);
				if (value instanceof NativeObject) {
					value = JsObjectToList(value);
				} else if (value instanceof Number) {
					value = new DFloNum(((Number) value).doubleValue());
				} else if (value instanceof NativeArray) {
					value = JsArrayToList(value);
				}
				result.add(YailList.makeList(new Object[] { key, value }));
			}
			return YailList.makeList(result);
		} else if (jsObject instanceof NativeArray) {
			return JsArrayToList(jsObject);
		}
		return jsObject;
	}

	public static void Log(String tag, String message) {
		RetValManager.appendReturnValue("", "ok", "来自扩展组件JsPlay的调试信息：\n" + "代码段名称为" + tag + "，执行结果：\n" + message);
	}
}
